Skip to content

Add support for Optional<T> in Dart generator (both dart and dart-dio) to distinguish absent, null, and present states#22257

Merged
wing328 merged 16 commits intoOpenAPITools:masterfrom
eirikb:feature/eirikb/dart-optional-patch
Feb 26, 2026
Merged

Add support for Optional<T> in Dart generator (both dart and dart-dio) to distinguish absent, null, and present states#22257
wing328 merged 16 commits intoOpenAPITools:masterfrom
eirikb:feature/eirikb/dart-optional-patch

Conversation

@eirikb
Copy link
Copy Markdown
Contributor

@eirikb eirikb commented Oct 30, 2025

fixes #21826

I saw in the guidelines that you guys don't want too many flags, well, here are two more:

  • useOptional=true: Enable Optional support all over, and
  • patchOnly=true: Automatically apply Optional only to only PATCH request schemas

The second one there might seem silly, but it makes a lot of sense to only support absent fields for PATCH.
I had a look at the Java Nullable thing and there it seems it is applied all over, at least when I generated.
This might be fine, but not always.

Without the flags the generated stuff should be as before.

This PR require Dart 3 Optional, as I use this to make fields optional.

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    

clean package works fine, the other two, I don't know what to do.

  • File the PR against the correct branch: master (upcoming 7.x.0 minor release - breaking changes with fallbacks), 8.0.x (breaking changes without fallbacks)

I guess...

  • If your PR solves a reported issue, reference it using GitHub's linking syntax (e.g., having "fixes #123" present in the PR description)
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

@josh-burton
@yissachar

Some details

Run like this:

openapi-generator-cli generate \
  -i spec.yaml \
  -g dart \
  -o output \
  --additional-properties=useOptional=true,patchOnly=true

Will produce output like this:

// POST request - regular nullable
class CreateUserRequest {
  String? email;
  String? name;
}

// PATCH request - uses Optional with nullable inner type
class UpdateUserRequest {
  Optional<String?> email;
  Optional<String?> name;
}

// Usage
final update = UpdateUserRequest(
  email: Optional.present('eirikb@eirikb.no'),  // Do update
  name: Optional.absent(),  // Don't update
);

Without the patchOnly=true flag all fields will be Optional.

Thanks :)

…io) to distinguish absent, null, and present states
…ns thing for testing (setting both options to "true" for both types)
@wing328
Copy link
Copy Markdown
Member

wing328 commented Nov 2, 2025

thanks for the pr

please follow step 3 to update the samples

@eirikb
Copy link
Copy Markdown
Contributor Author

eirikb commented Nov 2, 2025

thanks for the pr

please follow step 3 to update the samples

Awesome. I will look into it later today 👍

@eirikb eirikb force-pushed the feature/eirikb/dart-optional-patch branch from cdfa8fc to 57a8568 Compare November 2, 2025 18:43
@eirikb
Copy link
Copy Markdown
Contributor Author

eirikb commented Nov 3, 2025

Ok so I messed up and tries to revert a commit of 100 files because a lot of whitespace got added to the generated files. Thinking this was an issue with the genreator and not me I pushed only the related files.
I see now that it is because my my mustache changes:
image

I will tune the templates to not create the whitespace.
My last commit didn't help much. I will tune this even more later.

@wing328
Copy link
Copy Markdown
Member

wing328 commented Nov 3, 2025

👌

let us know if you need any help.

@wing328
Copy link
Copy Markdown
Member

wing328 commented Nov 10, 2025

cc @jaumard (2018/09) @josh-burton (2019/12) @amondnet (2019/12) @sbu-WBT (2020/12) @kuhnroyal (2020/12) @agilob (2020/12) @ahmednfwela (2021/08) for review

Copy link
Copy Markdown
Contributor

@amondnet amondnet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Add behavioral tests for:
    • useOptional flag wrapping non-required properties
    • patchOnly mode PATCH schema detection
    • patchOnly=true auto-enabling useOptional
    • Parameter unwrapping behavior
  2. Fix the logic gap where useOptional=true without patchOnly=true appears to do nothing
  3. Rename getString() to extractModelNameFromBodyParam() or similar

  * useOptional flag wrapping non-required properties
  * patchOnly mode PATCH schema detection
  * patchOnly=true auto-enabling useOptional
  * Parameter unwrapping behavior
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 19 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache:278">
P1: Inconsistent Optional handling for integer types: uses `json[...] == null` check instead of `json.containsKey()`. This conflates &#39;key absent&#39; with &#39;key present but null value&#39;, defeating the purpose of Optional&lt;T&gt; to distinguish these states. Other Optional cases in this template correctly use `containsKey()`.</violation>

<violation number="2" location="modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache:282">
P1: Inconsistent Optional handling for number types: uses `json[...] == null` check instead of `json.containsKey()`. This conflates &#39;key absent&#39; with &#39;key present but null value&#39;, defeating the purpose of Optional&lt;T&gt; to distinguish these states.</violation>
</file>

<file name="modules/openapi-generator/src/main/resources/dart/libraries/dio/optional.mustache">

<violation number="1" location="modules/openapi-generator/src/main/resources/dart/libraries/dio/optional.mustache:130">
P1: The `fromJson` method cannot distinguish between an absent field and a field explicitly set to `null`. When JSON contains `{&quot;field&quot;: null}`, this returns `Optional.absent()` instead of `Optional.present(null)`, defeating the core purpose of the Optional class.</violation>
</file>

<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java">

<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java:775">
P2: Adding schemas to `patchRequestSchemas` here has no effect because `postProcessModels` has already executed before `postProcessOperationsWithModels`. The `preprocessOpenAPI` method already handles PATCH schema identification at the right time. This code block is effectively dead code that misleads about its functionality.</violation>

<violation number="2" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java:813">
P2: Incorrect model name extraction for `Map&lt;K, V&gt;` types. This will extract `String, MyModel` instead of just `MyModel` for `Map&lt;String, MyModel&gt;`, causing the wrong schema name to be added to `patchRequestSchemas`. Consider parsing only the value type for Map (e.g., split by comma and take the last part).</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply to cubic to teach it or ask questions. Tag @cubic-dev-ai to re-run a review.

Comment thread modules/openapi-generator/src/main/resources/dart/libraries/dio/optional.mustache Outdated
@eirikb eirikb requested a review from amondnet December 30, 2025 10:12
@eirikb
Copy link
Copy Markdown
Contributor Author

eirikb commented Dec 30, 2025

The stuff that the cubic-dev-ai bot thing is talking about seems valid. Should I fix those?

… executed before postProcessOperationsWithModels.

And then we don't even need the extractModelNameFromBodyParam method...
@eirikb
Copy link
Copy Markdown
Contributor Author

eirikb commented Dec 30, 2025

Ok I went through the bot stuff and solved them.

The containsKey: Inconsistency fixed. I see the files has different patterns and I've just mixed them. My blames should be good now though.
Dio fromJson: This was a real bug, when using dio. Didn't notice at all - and one needs to actually run dart code to experience it. Fixed using sentinel pattern with readOptionalValue.
Dead code: I believe the bot is correct about timing, and thus we can remove the code - removed.

@konnic
Copy link
Copy Markdown

konnic commented Jan 28, 2026

Great PR @eirikb - I'd love to see this one get merged! Are you still actively working on it?

@eirikb
Copy link
Copy Markdown
Contributor Author

eirikb commented Jan 28, 2026

Great PR @eirikb - I'd love to see this one get merged! Are you still actively working on it?

As far as I know we are waiting for the reviewers to accept, unless I'm mistaken.

@wing328
Copy link
Copy Markdown
Member

wing328 commented Jan 28, 2026

thanks for the PR

will get this reviewed and merged soon.

(sorry for the delay as there are too many PRs...)

@pedrettin
Copy link
Copy Markdown

Any update on this? Would be great to see it merged 😃

@wing328
Copy link
Copy Markdown
Member

wing328 commented Feb 26, 2026

sorry please resolve the merge conflicts and 'll get it merge this weekend.

@wing328 wing328 added this to the 7.21.0 milestone Feb 26, 2026
…ure/eirikb/dart-optional-patch

# Conflicts:
#	modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache
@eirikb
Copy link
Copy Markdown
Contributor Author

eirikb commented Feb 26, 2026

Did a fetch of upstream master in IntelliJ and did a merge. Looks like it worked - but I got a bunch of newline changes. No idea why. I'll try to clean those up.

@eirikb
Copy link
Copy Markdown
Contributor Author

eirikb commented Feb 26, 2026

Done

@wing328 wing328 merged commit fbb99d2 into OpenAPITools:master Feb 26, 2026
15 checks passed
@wing328
Copy link
Copy Markdown
Member

wing328 commented Feb 26, 2026

PR merged.

please test the latest and let us know how that goes: https://github.com/OpenAPITools/openapi-generator/wiki/FAQ#how-to-test-with-the-latest-master-of-openapi-generator

davidricodias pushed a commit to davidricodias/openapi-generator that referenced this pull request Mar 11, 2026
…io) to distinguish absent, null, and present states (OpenAPITools#22257)

* Add support for `Optional<T>` in Dart generator (both dart and dart-dio) to distinguish absent, null, and present states

* Add useOptional and patchOnly options to the Dart client configurations thing for testing (setting both options to "true" for both types)

* Add documentation for useOptional and patchOnly options

* Tune the dart mustache (pluss class mustache) to get rid of the extra whitespace

* More tuning of the dart mustache files to adjust amount of whitespace - match previously generated setup

* Tune dart mustache templates to fix whitespace stuff by tips from wing328

* Fix the logic gap where useOptional=true without patchOnly=true appears to do nothing

* Rename getString() to extractModelNameFromBodyParam()

* Add behavioral tests

  * useOptional flag wrapping non-required properties
  * patchOnly mode PATCH schema detection
  * patchOnly=true auto-enabling useOptional
  * Parameter unwrapping behavior

* Fix inconsistency (my own) in native_class.mustache

* Remove "dead code" (because of timing). postProcessModels has already executed before postProcessOperationsWithModels.

And then we don't even need the extractModelNameFromBodyParam method...

* Fix Optional<T> to properly distinguish between absend and null

Had issues in dio

* Regenerate Dart samples

* Fix extra blank lines in dart-dio json_serializable template output
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REQ][dart-dio] Add JsonNullable-style presence wrapper (parity with Java openApiNullable)

5 participants